package cn.edu.suda.ada.database.dao;

import cn.edu.suda.ada.database.annotation.BeanUtils;
import cn.edu.suda.ada.database.bean.Bean;
import cn.edu.suda.ada.database.connection.Db;
import cn.edu.suda.ada.database.exception.DatabaseException;
import cn.edu.suda.ada.database.parser.DbParser;
import cn.edu.suda.ada.database.parser.DbParser.OPTIONS;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.*;

//TODO 实现 ActiveRecord 支持
/**
 * 基础数据库操作类
 * <P>
 * 对数据库的相关基本操作应当集中在该类的子类中，以实现 Thin Controllers & Fat Models
 * <P>
 * 主要提供了快捷的拼接SQL语句的功能及数据库操作的相关支持
 * 
 * @author JiangJun
 *
 */
public class Model {
	/**
	 * 日志
	 */
	protected static Logger logger = LogManager.getLogger("edu.suda.ada.database");
	/**
	 * 数据库连接类
	 */
	protected Db db = null;
	/**
	 * 数据库解析类
	 */
	protected DbParser dbParser = null;
	/**
	 * 设定增删改查的参数
	 */
	protected Map<OPTIONS, Object> options = new HashMap<OPTIONS, Object>();
	/**
	 * 执行的SQL
	 */
	protected String sql;

	/**
	 * 当前数据表名
	 */
	protected String tableName;
	/**
	 * 主键
	 */
	protected String key = "ID";

	/**
	 * 当前配置文件地址
	 */
	protected String path;

	public String getSql() {
		return sql;
	}

	public String getKey() {
		return key;
	}

	public void setKey(String key) {
		if (null != key) key = key.toUpperCase();
		this.key = key;
	}
	
	/**
	 * 初始化数据库连接
	 * 
	 * @throws DatabaseException 无法获取配置文件或无法关闭数据库连接
	 */
	protected void close() throws DatabaseException {
		db.close();
		options.clear();
		tableName();
	}
	
	/**
	 * 实例化
	 * 
	 * @param path 配置文件地址
	 * @param tableName 数据表名称
	 * @param key 主键名称
	 * @throws DatabaseException 无法获取配置文件或无法关闭数据库连接
	 */
	protected Model(String path, String tableName, String key) throws DatabaseException {
		switchInstance(path);
		setTableName(tableName);
		setKey(key);
	}
	
	/**
	 * 默认主键的实例化
	 * 
	 * @param path 配置文件地址
	 * @param tableName 数据表名称
	 * @throws DatabaseException 无法获取配置文件或无法关闭数据库连接
	 */
	protected Model(String path, String tableName) throws DatabaseException {
		switchInstance(path);
		setTableName(tableName);
	}
	
	/**
	 * 实例化
	 * 
	 * @param path 配置文件地址
	 * @throws DatabaseException 无法获取配置文件或无法关闭数据库连接
	 */
	protected Model(String path) throws DatabaseException {
		switchInstance(path);
	}
	
	/**
	 * 实例化
	 * 
	 * @throws DatabaseException 无法获取配置文件或无法关闭数据库连接
	 */
	protected Model() throws DatabaseException {
		switchInstance(null);
	}
	
	/**
	 * 切换回默认配置数据库
	 * 
	 * @return 新的数据库实例
	 * @throws DatabaseException 无法获取配置文件
	 */
	public Model getDefaultInstance() throws DatabaseException {
		return switchInstance(null);
	}
	
	/**
	 * 切换数据库
	 * 
	 * @param path 数据库配置文件地址
	 * @return 新的数据库实例
	 * @throws DatabaseException 无法获取配置文件
	 */
	public Model switchInstance(String path) throws DatabaseException {
		this.path = path;
		db = Db.getInstance(path);
		dbParser = DbParser.getInstance(path);
		return this;
	}
	
	/**
	 * 获得当前连接数据库名称
	 * 
	 * @return 当前连接数据库名称
	 */
	public String getDatabaseName() {
		return db.getDatabase();
	}

	/**
	 * 获得当前数据库中的数据表前缀
	 *
	 * @return 当前数据库表前缀
     */
	public String getTablePrefix() {
		return db.getPrefix();
	}
	
	// 设置参数相关
	/**
	 * 设置查询的唯一字段
	 * 
	 * @param field 查询唯一字符串
	 * @return 当前对象引用
	 */
	public Model distinct(String field) {
		options.put(OPTIONS.DISTINCT, field);
		return this;
	}
	
	/**
	 * 设置查询字段
	 * 
	 * @param field 查询字段
	 * @return 当前对象引用
	 */
	public Model field(String field) {
		options.put(OPTIONS.FIELD, field);
		return this;
	}
	
	/**
	 * 获得实际的表名
	 * 
	 * @param tableName 表名
	 * @return 实际的表名
	 */
	protected String getRealTableName(String tableName) {
		String prefix = getTablePrefix();
		if (StringUtils.startsWith(tableName, prefix)) {
			return tableName;
		} else {
			return prefix + tableName;
		}
	}
	
	/**
	 * 设置表名
	 * 
	 * @param tableName 数据表名
	 * @return 当前对象引用
	 */
	public Model setTableName(String tableName) {
		tableName = getRealTableName(tableName);
		this.tableName = tableName;

		return tableName();
	}

	public String getTableName() {
		return tableName;
	}

	/**
	 * 设置表名
	 * <P>
	 * <code>setTableName</code> 的别名
	 *
	 * @see Model setTableName
	 * @param tableName 数据表名
	 * @return 当前对象引用
	 */
	public Model tableName(String tableName) {
		return this.setTableName(tableName);
	}
	
	/**
	 * 重置表名
	 * 
	 * @return 当前对象引用
	 */
	private Model tableName() {
		options.put(OPTIONS.TABLE, tableName);
		return this;
	}
	
	/**
	 * 查询条件
	 * 
	 * @param key 属性键
	 * @param value 属性值
	 * @return 当前对象引用
	 */
	public Model where(String key, Object value) {
		@SuppressWarnings("unchecked")
		Map<String, Object> where = (Map<String, Object>) options.get(OPTIONS.WHERE);
		if (null == where) {
			where = new HashMap<String, Object>();
			options.put(OPTIONS.WHERE, where);
		}
		where.put(key, value);
		return this;
	}
	
	/**
	 * 查询条件
	 * 
	 * @param where 查询条件字符串
	 * @return 当前对象引用
	 */
	public Model where(String where) {
		this.where(DbParser.WHERE_STRING, where);
		return this;
	}
	
	/**
	 * 排序条件
	 * 
	 * @param order 排序条件字符串
	 * @return 当前对象引用
	 */
	public Model order(String order) {
		options.put(OPTIONS.ORDER, order);
		return this;
	}
	
	/**
	 * 分页条件
	 * 
	 * @param limit 可以为字符串，也可以为每页数量数字
	 * @return 当前对象引用
	 */
	public Model limit(int limit) {
		options.put(OPTIONS.LIMIT, limit);
		return this;
	}
	
	/**
	 * 分页条件
	 * 
	 * @param page 当前页
	 * @param limit 每页数量
	 * @return 当前对象引用
	 */
	public Model limit(int page, int limit) {
		this.offset((page - 1) * limit, limit);
		return this;
	}
	
	/**
	 * 指定偏移
	 * 
	 * @param offset 当前偏移量
	 * @param limit 每页数量
	 * @return 当前对象引用
	 */
	public Model offset(int offset, int limit) {
		options.put(OPTIONS.LIMIT, limit + " " + DbParser.OFFSET_KEYWORD + " " + offset);
		return this;
	}
	
	/**
	 * 新增/更新数据
	 * 
	 * @param data 数据map
	 * @return 当前对象引用
	 */
	public Model data(Map<String, Object> data) {
		options.put(OPTIONS.DATA, data);
		return this;
	}
	
	// TODO JOIN

	// TODO GROUP

	// TODO HAVING

	// TODO UNION

	// 数据查询相关
	/**
	 * 记录条数
	 * 
	 * @return 符合查询的记录条数
	 * @throws DatabaseException 数据库错误
	 */
	public int count() throws DatabaseException {
		return this.count("*");
	}

	/**
	 * 记录条数
	 * 
	 * @param field 查询字段
	 * @return 符合查询的记录条数
	 * @throws DatabaseException 数据库错误
	 */
	public int count(String field) throws DatabaseException {
		sql = dbParser.count(options, field);
		this.log();

		try {
			ResultSet rs = db.executeQuery(sql);
			if (rs.next()) {
				return rs.getInt(1);
			}
		} catch (SQLException e) {
			throw new DatabaseException(e);
		} finally {
			close();
		}
		return -1;
	}

	/**
	 * 返回结果集
	 * 
	 * @return 结果集
	 * @throws SQLException 数据库错误
	 * @throws DatabaseException 
	 */
	private ResultSet getResultSet() throws SQLException, DatabaseException {
		if (null == tableName) {
			throw new DatabaseException("请先设置具体的数据表名称！");
		}
		sql = dbParser.select(options);
		this.log();

		return db.executeQuery(sql);
	}
	
	/**
	 * 返回结果集队列
	 * 
	 * @return 结果集队列
	 * @throws DatabaseException 数据库错误
	 */
	protected List<Map<String, Object>> getResultList() throws DatabaseException {
		ResultSet rs = null;
		try {
			rs = db.executeQuery(sql);
		} catch (SQLException e) {
			throw new DatabaseException(e);
		}
		List<Map<String, Object>> resultList = null;
		try {
			resultList = this.resultSetToList(rs);
		} catch (SQLException e) {
			throw new DatabaseException(e);
		}
		return resultList;
	}
	
	/**
	 * 返回数据列表
	 * <P>
	 * <b>注意：键均以大写形式出现</b>。
	 * 
	 * @return 数据列表队列
	 * @throws DatabaseException 数据库错误
	 */
	public List<Map<String, Object>> select() throws DatabaseException {
		try {
			return resultSetToList(this.getResultSet());
		} catch (SQLException e) {
			throw new DatabaseException(e);
		} finally {
			close();
		}
	}
	
	/**
	 * 返回数据 Bean 列表
	 * 
	 * @param beanClass 持久化类
	 * @return 数据列表队列
	 * @throws DatabaseException 数据库错误
	 */
	public <T extends Bean> List<T> selectBeans(Class<? extends Bean> beanClass) throws DatabaseException {
		List<Map<String, Object>> list = select();
		if (null != list && !list.isEmpty()) {
			List<T> beanList = new ArrayList<T>();
			for(Map<String, Object> item : list) {
				beanList.add(mapToBean(beanClass, item));
			}
			return beanList;
		}
		return null;
	}
	
	/**
	 * 查询主键元组对
	 * <P>
	 * <b>注意：键均以大写形式出现</b>
	 * 
	 * @return 数据主键元组对
	 * @throws DatabaseException 数据库错误
	 */
	public Map<String, Map<String, Object>> map() throws DatabaseException{
		try {
			return resultSetToMap(this.getResultSet());
		} catch (SQLException e) {
			throw new DatabaseException(e);
		} finally {
			close();
		}
	}
	
	/**
	 * 查询主键 Bean 对 
	 * 
	 * @param beanClass 持久化类
	 * @return 数据列表队列
	 * @throws DatabaseException 数据库错误
	 */
	public <T extends Bean> Map<String, T> mapBeans(Class<? extends Bean> beanClass) throws DatabaseException {
		Map<String, Map<String, Object>> map = map();
		if (null != map && !map.isEmpty()) {
			Map<String, T> beanMap = new LinkedHashMap<String, T>();
			for(String key : map.keySet()) {
				beanMap.put(key, mapToBean(beanClass, map.get(key)));
			}
			return beanMap;
		}
		return null;
	}
	
	/**
	 * 获得某个字段值的集合
	 * 
	 * @param <T> 字段类型
	 * @param field 字段
	 * @return 字段值的集合
	 * @throws DatabaseException 数据库错误
	 */
	@SuppressWarnings("unchecked")
	public <T> Set<T> set(String field) throws DatabaseException {
		this.field(field);
		ResultSet rs;
		try {
			rs = this.getResultSet();
		} catch (SQLException e) {
			throw new DatabaseException(e);
		}
		Set<T> set = new HashSet<T>();
		try {
			if (1 != rs.getMetaData().getColumnCount()) return null;
			while(rs.next()) {
				set.add((T)rs.getString(1));
			}
			return set;
		} catch (SQLException e) {
			throw new DatabaseException(e);
		} finally {
			close();
		}
	}
	
	/**
	 * 获得键值对
	 * 
	 * @param <K> 键类型
	 * @param <V> 值类型
	 * @param key 键字段
	 * @param value 值字段
	 * @return 键值对
	 * @throws DatabaseException 数据库错误
	 */
	@SuppressWarnings("unchecked")
	public <K, V> Map<K, V> keyValue(String key, String value) throws DatabaseException {
		this.field(key + "," + value);
		ResultSet rs;
		try {
			rs = this.getResultSet();
		} catch (SQLException e) {
			throw new DatabaseException(e);
		}
		Map<K, V> map = new HashMap<K, V>();
		try {
			if (2 != rs.getMetaData().getColumnCount()) return null;
			while(rs.next()) {
				map.put((K)rs.getString(1), (V)rs.getString(2));
			}
			return map;
		} catch (SQLException e) {
			throw new DatabaseException(e);
		} finally {
			close();
		}
	}

	/**
	 * 将结果集转化为队列 <code>List</code>
	 * <P>
	 * <b>注意：键均以大写形式出现</b>
	 * 
	 * @param rs 结果集
	 * @return 结果集队列
	 * @throws SQLException 数据库错误
	 */
	protected List<Map<String, Object>> resultSetToList(ResultSet rs) throws SQLException {
		List<Map<String, Object>> resultList = new ArrayList<Map<String, Object>>();
		ResultSetMetaData rsmd = rs.getMetaData();
		int cols = rsmd.getColumnCount();
		while (rs.next()) {
			Map<String, Object> resultRow = new LinkedHashMap<String, Object>();
			for (int i = 1; i <= cols; i++) {
				resultRow.put(rsmd.getColumnName(i).toUpperCase(), rs.getString(i));
			}
			resultList.add(resultRow);
		}
		return resultList;
	}
	
	/**
	 * 将结果集转化为表 <code>Map</code>
	 * <P>
	 * <b>注意：键均以大写形式出现</b>
	 * 
	 * @param rs 结果集
	 * @return 结果集map
	 * @throws SQLException 数据库错误
	 */
	private Map<String, Map<String, Object>> resultSetToMap(ResultSet rs) throws SQLException {
		Map<String, Map<String, Object>> resultMap = new LinkedHashMap<String, Map<String, Object>>();
		ResultSetMetaData rsmd = rs.getMetaData();
		int cols = rsmd.getColumnCount();
		while (rs.next()) {
			Map<String, Object> resultRow = new LinkedHashMap<String, Object>();
			for (int i = 1; i <= cols; i++) {
				resultRow.put(rsmd.getColumnName(i).toUpperCase(), rs.getString(i));
			}
			resultMap.put(resultRow.get(key).toString(), resultRow);
		}
		return resultMap;
	}

	/**
	 * 获得第一条
	 * 
	 * @return 第一条记录，不存在返回空
	 * @throws DatabaseException 数据库错误
	 */
	public Map<String, Object> find() throws DatabaseException {
		if (null == options.get(OPTIONS.LIMIT)) this.limit(1);
		List<Map<String, Object>> resultList = this.select();

		if (null != resultList && !resultList.isEmpty()) {
			return resultList.get(0);
		}
		return null;
	}

	// 数据库操作
	/**
	 * 删除记录
	 * 
	 * @return 受影响行数
	 * @throws DatabaseException 数据库错误
	 */
	public int delete() throws DatabaseException {
		if (null == tableName) {
			throw new DatabaseException("请先设置具体的数据表名称！");
		}
		try {
			sql = dbParser.delete(options);
			this.log();

			return db.executeUpdate(sql);
		} catch (SQLException e) {
			throw new DatabaseException(e);
		} finally {
			close();
		}
	}

	/**
	 * 新增记录
	 * 
	 * @param data 数据
	 * @return 受影响行数
	 * @throws DatabaseException 数据库错误
	 */
	public int insert(Map<String, Object> data) throws DatabaseException {
		if (null == tableName) {
			throw new DatabaseException("请先设置具体的数据表名称！");
		}
		try {
			sql = dbParser.insert(options, data);
			this.log();

			return db.executeUpdate(sql);
		} catch (SQLException e) {
			throw new DatabaseException(e);
		} finally {
			close();
		}
	}
	
	/**
	 * 新增记录
	 * 
	 * @return 受影响行数
	 * @throws DatabaseException 数据库错误
	 */
	public int insert() throws DatabaseException {
		if (null == tableName) {
			throw new DatabaseException("请先设置具体的数据表名称！");
		}
		try {
			sql = dbParser.insert(options);
			this.log();

			return db.executeUpdate(sql);
		} catch (SQLException e) {
			throw new DatabaseException(e);
		} finally {
			close();
		}
	}
	
	/**
	 * 新增记录
	 * 
	 * @param bean 数据 Bean
	 * @return 受影响行数
	 * @throws DatabaseException
	 */
	public int insertBean(Bean bean) throws DatabaseException {
		return this.insert(beanToMap(bean));
	}
	
	/**
	 * 批量插入
	 * 
	 * @param list 批量数据
	 * @return 受影响行数
	 * @throws DatabaseException 数据库错误
	 */
	public int insertAll(List<Map<String, Object>> list) throws DatabaseException {
		if (null == tableName) {
			throw new DatabaseException("请先设置具体的数据表名称！");
		}
		if (null == list || list.isEmpty()) {
			throw new DatabaseException("请设置需要插入的数据！");
		}
		try {
			db.startTransaction();
			for (Map<String, Object> data : list) {
				String sql = dbParser.insert(options, data);
				logger.info(sql);
				db.addBatch(sql);
			}
			int total = db.executeBatchUpdate();
			db.commit();
			return total;
		} catch (SQLException e) {
			throw new DatabaseException(e);
		} finally {
			close();
		}
	}
	
	/**
	 * 批量插入
	 * 
	 * @param beanList 数据 Bean 列表
	 * @return 受影响行数
	 * @throws DatabaseException 数据库错误
	 */
	public int insertBeans(List<? extends Bean> beanList) throws DatabaseException {
		if (null == beanList || beanList.isEmpty()) {
			throw new DatabaseException("请设置需要插入的数据！");
		}
		List<Map<String, Object>> listData = new LinkedList<Map<String, Object>>();
		for(Bean bean : beanList) {
			listData.add(beanToMap(bean));
		}
		return insertAll(listData);
	}
	
	/**
	 * 更新记录
	 * 
	 * @param data 数据
	 * @return 受影响行数
	 * @throws DatabaseException 数据库错误
	 */
	public int update(Map<String, Object> data) throws DatabaseException {
		if (null == tableName) {
			throw new DatabaseException("请先设置具体的数据表名称！");
		}
		try {
			sql = dbParser.update(options, data);
			this.log();
			
			return db.executeUpdate(sql);
		} catch (SQLException e) {
			throw new DatabaseException(e);
		} finally {
			close();
		}
	}
	
	/**
	 * 更新记录
	 * 
	 * @return 受影响行数
	 * @throws DatabaseException 数据库错误
	 */
	public int update() throws DatabaseException {
		if (null == tableName) {
			throw new DatabaseException("请先设置具体的数据表名称！");
		}
		try {
			sql = dbParser.update(options);
			this.log();

			return db.executeUpdate(sql);
		} catch (SQLException e) {
			throw new DatabaseException(e);
		} finally {
			close();
		}
	}
	
	/**
	 * 更新记录
	 * 
	 * @param bean 数据
	 * @return 受影响行数
	 * @throws DatabaseException 数据库错误
	 */
	public int updateBean(Bean bean) throws DatabaseException {
		return update(beanToMap(bean));
	}

	/**
	 * 获得持久化对象
	 * @param <T> 参数
	 *  
	 * @param beanClass 持久化类
	 * @param map 数据
	 * @return 持久化对象实例
	 * @throws DatabaseException 转换错误
	 */
	@SuppressWarnings("unchecked")
	public <T extends Bean> T mapToBean(Class<? extends Bean> beanClass, Map<String, Object> map) throws DatabaseException {
		return (T) BeanUtils.getBean(beanClass, map);
	}
	
	/**
	 * 根据对象获得 <code>Map</code> 数据
	 * 
	 * @param bean 持久化对象实例
	 * @return <code>Map</code> 数据
	 * @throws DatabaseException 转换错误
	 */
	public Map<String, Object> beanToMap(Bean bean) throws DatabaseException {
		return BeanUtils.getMap(bean);
	}
	
	/**
	 * 记录日志
	 */
	protected void log() {
		logger.info(sql);
	}
}
